package beautiful.butterfly.drds.datasource.connection_contexts.spi;

//import beautiful.butterfly.drds.datasource.author.Author;
//import beautiful.butterfly.drds.datasource.author.Authors;

import beautiful.butterfly.drds.datasource.connection_contexts.api.IConnectionContext;
import beautiful.butterfly.drds.datasource.connection_contexts.api.IParameterizedISql;
import beautiful.butterfly.drds.datasource.connection_contexts.api.ISql;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.*;
import java.util.concurrent.locks.ReentrantLock;

//@Author(name = Authors.LI_YANG)
public class ConnectionContext implements IConnectionContext {
    public static final Logger logger = LoggerFactory.getLogger(ConnectionContext.class);
    public ReentrantLock lock = new ReentrantLock();
    //
    protected volatile SharedConnection sharedConnection;
    protected Connection connection;
    private final Thread getThread;// 获取线程

    // 开启追踪
    private volatile boolean traceEnable = false;
    private boolean disable = false;
    private boolean closed = false;

    private long connectedTimeNano;
    /**
     * 核心参数
     */
    private volatile boolean running = false;
    public volatile boolean abandoned = false;

    private Exception exception = null;

    public ConnectionContext(SharedConnection sharedConnection) {
        super();
        this.sharedConnection = sharedConnection;
        this.connection = sharedConnection.getConnection();
        this.getThread = Thread.currentThread();
    }

    public SharedConnection getSharedConnection() {
        return sharedConnection;
    }

    public Connection getConnection() {
        return connection;
    }

    public Thread getGetThread() {
        return this.getThread;
    }

    //

    public void setConnectedTimeNano(long connectedTimeNano) {
        this.connectedTimeNano = connectedTimeNano;
    }

    public long getConnectedTimeNano() {
        return connectedTimeNano;
    }

    //
    public void setTraceEnable(boolean traceEnable) {
        this.traceEnable = traceEnable;
    }

    public boolean isTraceEnable() {
        return traceEnable;
    }

    //
    public SQLException handleException(Exception exception) throws SQLException {
        final SharedConnection sharedConnection = this.sharedConnection;
        //
        if (sharedConnection != null) {
            ConnectionContexts connectionContexts = (ConnectionContexts) sharedConnection.getAbstractConnectionContexts();
            connectionContexts.handleConnectionException(this, null);
        }
        if (exception instanceof SQLException) {
            throw (SQLException) exception;
        }
        throw new RuntimeException("exception", exception);
    }

    public void disable(Exception exception) {
        if (exception == null) {
            throw new NullPointerException(Exception.class.getSimpleName());
        }
        this.exception = exception;
        //
        this.disable = true;
        this.sharedConnection = null;
        this.traceEnable = false;

    }

    public Exception getException() {
        return this.exception;
    }

    public boolean isDisable() {
        return disable;
    }

    //
    public void check() throws SQLException {
        if (this.sharedConnection != null) {
            this.lock.lock();
            try {
                checkInternal();
            } finally {
                this.lock.unlock();
            }
        } else {
            checkInternal();
        }
    }

    private void checkInternal() throws SQLException {
        if (this.sharedConnection == null) {
            if (this.exception != null) {
                throw new RuntimeException("connection sharedConnection is null", exception);
            } else {
                throw new RuntimeException("connection sharedConnection is null");
            }
        }

        if (this.closed) {
            if (this.exception != null) {
                throw new RuntimeException("connection closed", exception);
            } else {
                throw new SQLException("connection closed");
            }
        }

        if (this.disable) {
            if (this.exception != null) {
                throw new RuntimeException("connection disabled", exception);
            } else {
                throw new SQLException("connection disabled");
            }
        }
    }

    //

    public void close() {
        logger.info("关闭连接");
        if (this.disable) {
            return;
        }
        SharedConnection sharedConnection = this.sharedConnection;
        if (sharedConnection == null) {
            return;
        }
        //
        boolean isSameThread = this.getGetThread() == Thread.currentThread();
        if (!isSameThread) {
            closeWithLock();
            return;
        } else {
            recycle();
            this.disable = true;
        }

    }

    public void closeWithLock() {
        lock.lock();
        try {
            recycle();
            this.disable = true;
        } finally {
            lock.unlock();
        }
    }

    public void recycle() {
        if (!this.abandoned) {
            AbstractConnectionContexts abstractConnectionContexts = sharedConnection.getAbstractConnectionContexts();
            abstractConnectionContexts.recycle(this);
        }
        this.sharedConnection = null;
        this.connection = null;
        this.closed = true;
    }

    public boolean isClosed() throws SQLException {
        if (this.sharedConnection == null) {
            return true;
        }
        return this.connection.isClosed();
    }


    //
    public ISql createSql() throws SQLException {
        check();

        Statement statement = null;
        try {
            statement = this.connection.createStatement();
        } catch (SQLException ex) {
            handleException(ex);
        }

        initStatement(statement, this.sharedConnection.getAbstractConnectionContexts());

        Sql sql = new Sql(this, statement);
        this.sharedConnection.addSql(sql);
        return sql;
    }

    public IParameterizedISql createParameterizedSql(String sql) throws SQLException {
        check();
        PreparedStatement preparedStatement = this.sharedConnection.getConnection().prepareStatement(sql);
        ParameterizedSql parameterizedSql = new ParameterizedSql(this, preparedStatement);
        sharedConnection.addSql(parameterizedSql);
        return parameterizedSql;
    }

    public IParameterizedISql createParameterizedSql(String sql, int autoGeneratedKeys) throws SQLException {
        check();
        PreparedStatement preparedStatement = this.sharedConnection.getConnection().prepareStatement(sql, autoGeneratedKeys);
        ParameterizedSql parameterizedSql = new ParameterizedSql(this, preparedStatement);
        this.sharedConnection.addSql(parameterizedSql);
        return parameterizedSql;
    }

    public DatabaseMetaData getDatabaseMetaData() throws SQLException {
        check();
        return this.connection.getMetaData();
    }

    //
    public void beforeExecute() {
        final SharedConnection sharedConnection = this.sharedConnection;
        if (sharedConnection != null) {
            this.running = true;
        }
    }

    public void afterExecute() {
        final SharedConnection sharedConnection = this.sharedConnection;
        if (sharedConnection != null) {
            this.running = false;
            sharedConnection.setLastActiveTimeMillis(System.currentTimeMillis());
        }
    }

    public boolean isRunning() {
        return this.running;
    }

    //
    public void setReadOnly(boolean readOnly) throws SQLException {
        check();

        if (readOnly == this.sharedConnection.isReadOnly()) {
            return;
        }
        try {
            this.connection.setReadOnly(readOnly);
        } catch (SQLException ex) {
            handleException(ex);
        }
        this.sharedConnection.setReadOnly(readOnly);
    }

    public boolean isReadOnly() throws SQLException {
        check();

        return this.connection.isReadOnly();
    }


    //
    public void setAutoCommit(boolean autoCommit) throws SQLException {
        check();

        if (autoCommit == this.sharedConnection.isAutoCommit()) {
            return;
        }

        try {
            this.connection.setAutoCommit(autoCommit);
            this.sharedConnection.setAutoCommit(autoCommit);
        } catch (SQLException ex) {
            handleException(ex);
        }
    }

    public boolean getAutoCommit() throws SQLException {
        check();

        return this.connection.getAutoCommit();
    }

    //
    public void commit() throws SQLException {
        check();

        try {
            this.connection.commit();
        } catch (SQLException ex) {
            handleException(ex);
        }
    }

    public void rollback() throws SQLException {

        if (this.sharedConnection == null) {
            return;
        }
        try {
            this.connection.rollback();
        } catch (SQLException ex) {
            handleException(ex);
        }
    }

    public void initStatement(Statement statement, AbstractConnectionContexts abstractConnectionContexts) throws SQLException {
        boolean isAutoCommit = !getSharedConnection().isAutoCommit();
        int queryTimeout = isAutoCommit ? abstractConnectionContexts.getTransactionQueryTimeout() : abstractConnectionContexts.getQueryTimeout();
        if (queryTimeout > 0) {
            statement.setQueryTimeout(queryTimeout);
        }
    }
}
